package com.os.common.utils;

import cn.hutool.core.collection.ListUtil;
import com.csvreader.CsvReader;
import com.os.common.exception.ErrorException;
import org.apache.poi.POIXMLDocumentPart;
import org.apache.poi.hssf.usermodel.*;
import org.apache.poi.ss.usermodel.*;
import org.apache.poi.xssf.usermodel.*;

import javax.servlet.http.HttpServletResponse;
import java.io.BufferedReader;
import java.io.IOException;
import java.io.InputStream;
import java.io.InputStreamReader;
import java.math.BigDecimal;
import java.nio.charset.StandardCharsets;
import java.text.SimpleDateFormat;
import java.util.ArrayList;
import java.util.Date;
import java.util.LinkedList;
import java.util.List;

/**
 * 描述：文件工具类
 *
 * @author huxuehao
 **/
public class FileUtil {

    private static final String INT = "int";
    private static final String BIGINT = "bigint";
    private static final String DECIMAL = "decimal";
    private static final String DATE = "date";
    private static final String DATETIME = "datetime";
    private static final String TIME = "time";
    private static final String VARCHAR = "varchar";

    /**
     * 设置下载的响应头
     */
    public static void setDownloadHeader(HttpServletResponse response) {
        response.setCharacterEncoding("UTF-8");
        response.setHeader("download","true");
        response.setContentType("application/octet-stream;charset=UTF-8");
    }
    /**
     * 从txt中读取数据
     * @param regx 数据分隔符
     * @param inputStream 数据流
     */
    public static List<List<Object>> getDataFromTxt(String regx, InputStream inputStream) throws Exception{

        /* 获取所有记录 */
        List<List<Object>> arrList = new LinkedList<>();
        try(BufferedReader bufferedReader = new BufferedReader(new InputStreamReader(inputStream))) {

            String str;
            while((str = bufferedReader.readLine()) != null) {
                arrList.add(ListUtil.toList(str.split(regx)));
            }
        } catch (IOException e) {
            throw new Exception("抱歉，文件流异常");
        }

        /* 填充字段类型 */
        return fillFieldType(arrList);
    }

    /**
     * 从csv中读取数据
     */
    public static List<List<Object>> getDataFromCsv(InputStream inputStream) throws Exception{
        CsvReader csvReader = new CsvReader(inputStream, ',', StandardCharsets.UTF_8);

        /* 获取所有记录 */
        List<List<Object>> arrList = new LinkedList<>();
        while (csvReader.readRecord()) {
            arrList.add(ListUtil.toList(csvReader.getValues()));
        }

        /* 填充字段类型 */
        return fillFieldType(arrList);
    }

    /**
     * 填充字段类型
     */
    private static List<List<Object>> fillFieldType(List<List<Object>> arrList){
        for (int i = 1; i < arrList.size(); i++) {
            List<Object> list = arrList.get(i);
            ArrayList<Object> typeList = new ArrayList<>();
            if ((!list.contains(null) && !list.contains("")) || (i+1 == arrList.size())) {
                for (Object s : arrList.get(i)) {
                    typeList.add(MyUtil.StringToMySqlType(s.toString()));
                }
                arrList.add(typeList);
                break;
            }
        }
        return arrList;
    }

    /**
     * 获取第一sheet的名字，用作默认表名
     * @param xls         true xls文件，false xlsx文件
     * @param inputStream 文件输入流
     * @return 第一sheet的名字
     */
    public static String getFirstSheetNameOfExcel(boolean xls, InputStream inputStream) throws Exception{
        if (xls) {
            return new HSSFWorkbook(inputStream).getSheetAt(0).getSheetName();
        } else {
            return new XSSFWorkbook(inputStream).getSheetAt(0).getSheetName();
        }
    }

    /**
     * 从excel中读取数据
     * @param isXls         true xls文件，false xlsx文件
     * @param inputStream   文件输入流
     * @return 数据封装到list，List(0)中存放是的表头
     */
    public static List<List<Object>> getDataFromExcel(boolean isXls, InputStream inputStream) throws Exception {
        List<List<Object>> dataList = new LinkedList<>();

        /* 获取第一个工作sheet */
        Sheet sheet;
        if (isXls) {
            sheet = new HSSFWorkbook(inputStream).getSheetAt(0);
        } else {
            sheet = new XSSFWorkbook(inputStream).getSheetAt(0);
        }

        /* 获取有效列 */
        int cellNums;
        if (sheet.getRow(0) == null) { /* 判断表头是否正确 */
            throw new ErrorException("抱歉，excel表头存在问题");
        } else if (sheet.getRow(0).getPhysicalNumberOfCells() < 1){  /* 判断表头是否正确 */
            throw new ErrorException("抱歉，excel表头存在问题");
        } else {
            cellNums =  sheet.getRow(0).getPhysicalNumberOfCells();
        }

        /* 判断excel中是否存在照片 */
        if (isXls && havePicture((HSSFSheet) sheet)) {
            throw new ErrorException("抱歉，暂不支持Excel中存在图片");
        } else {
            assert sheet instanceof XSSFSheet;
            if (havePicture((XSSFSheet) sheet)){
                throw new ErrorException("抱歉，暂不支持Excel中存在图片");
            }
        }

        /* 获取数据 */
        boolean notGetFieldType = true;
        List<Object> fieldType = new ArrayList<>();
        for (int i = 0; i <= sheet.getLastRowNum(); i++) {
            Row row = sheet.getRow(i);
            if (row == null) {
                throw new ErrorException("抱歉，不可存在空间断为空，第" + (i+1) + "行为空");
            }
            List<Object> subList = new ArrayList<>();
            for (int j = 0; j < cellNums; j++){
                /* 获取cell中的数据，并添加到subList中 */
                subList.add(getCellValue(row.getCell(j)));
            }
            dataList.add(subList);

            /* 获取字段类型（为了提高整个方法的效率，只按照最后一条记录进行判断字段类型） */
            if (notGetFieldType && !subList.contains(null) && i > 0) {
                fieldType= getRowType(row, cellNums);
                notGetFieldType = false;

            } else if (notGetFieldType && i == sheet.getLastRowNum()) {
                fieldType = getRowType(row, cellNums);
                notGetFieldType = false;
            }
            /* 最后将字段类型插入到dataList */
            if (i == sheet.getLastRowNum()) {
                dataList.add(fieldType);
            }

        }
        return dataList;
    }
 
    /* 判断xls中是否存在照片 */
    private static boolean havePicture(HSSFSheet sheet) {
        List<HSSFShape> list = sheet.getDrawingPatriarch().getChildren();
        for (HSSFShape shape : list) {
            // 存在图片
            if (shape instanceof HSSFPicture) {
                return true;
            }
        }
        return false;
    }
    /* 判断xlsx中是否存在照片 */
    private static boolean havePicture(XSSFSheet sheet) {
        List<POIXMLDocumentPart> list = sheet.getRelations();
        for (POIXMLDocumentPart part : list) {
            if (part instanceof XSSFDrawing) {
                return true;
            }
        }
        return false;
    }

    /**
     * 获取Cell中的值
     * 因为POI读取 Excel 中的数字、日期、时间均以数值类型进行存储，所以就要区分，到底是数据还是时间日期
     * 1. 可以通过 DateUtil.isCellDateFormatted(cell) 来区分类型的数据是否是时间日期
     * 2. 当是时间日期时，可以通过short format = cell.getCellStyle().getDataFormat() 来获取时间日期的格式，
     *    format 值对应的格式如下：
     *    yyyy-MM-dd----- 14
     *    yyyy年m月d日---- 31
     *    yyyy年m月------- 57
     *    m月d日  ------- 58
     *    HH:mm--------- 20
     *    h时mm分  ------ 32
     * @param cell POI中的一个单元格
     */
    private static Object getCellValue(Cell cell) throws Exception {
        if (cell == null) {
            return null;
        }
        /* 判断该单元格的数据类型 */
        switch (cell.getCellTypeEnum()) {
            case STRING: /* 字符串 */
                return cell.getStringCellValue();
            case NUMERIC: /* 数字 */
                /* 当是日期格式时 */
                if (DateUtil.isCellDateFormatted(cell)) {
                    /* 用于格式化日期 */
                    SimpleDateFormat sdf;
                    /* 获取POI中日期格式编号 */
                    short format = cell.getCellStyle().getDataFormat();
                    if (format == 20 || format == 32) {
                        sdf = new SimpleDateFormat("HH:mm");
                    } else if (format == 14 || format == 31 || format == 57 || format == 58) {
                        /*  处理自定义日期格式：m月d日(通过判断单元格的格式id解决，id的值是58) */
                        sdf = new SimpleDateFormat("yyyy-MM-dd");
                        double value = cell.getNumericCellValue();
                        Date date = org.apache.poi.ss.usermodel.DateUtil
                                .getJavaDate(value);
                        return sdf.format(date);
                    }else { /* 日期 */
                        sdf = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss");
                    }
                    try {
                        return sdf.format(cell.getDateCellValue()); /* 日期 */
                    } catch (Exception e) {
                        throw new Exception("抱歉，获取日期数据异常");
                    }
                }
                /* 当是普通数字时 */
                else {
                    return BigDecimal.valueOf(cell.getNumericCellValue());
                    // return bd.toPlainString();// 数值 这种用BigDecimal包装再获取plainString，可以防止获取到科学计数值
                }
            case BLANK: /* 空白 */
                return "";
            case BOOLEAN: /* bool类型*/
                return cell.getBooleanCellValue();
            case FORMULA: /* 公式 */
                throw new Exception("抱歉，不支持Excel中存在公式");
            default:
                throw new Exception("抱歉，暂时不支持Excel中存的部分格式");
        }
    }

    /**
     * 获取字段类型
     * @param row      excel行元素
     * @param cellNums excel单元格
     */
    private static List<Object> getRowType(Row row, int cellNums) throws Exception {
        ArrayList<Object> type = new ArrayList<>();
        for (int j = 0; j < cellNums; j++){
            Cell cell = row.getCell(j);
            if (cell == null) {
                type.add(j, VARCHAR);
                continue;
            }
            switch (cell.getCellTypeEnum()) {
                case STRING: /* 字符串 */
                case BLANK: /* 空白 */
                case BOOLEAN: /* bool类型 */
                    type.add(j, VARCHAR);
                    break;
                case NUMERIC: /* 数字 */
                    /* 当是日期格式时 */
                    if (DateUtil.isCellDateFormatted(cell)) {
                        short format = cell.getCellStyle().getDataFormat();
                        if (format == 20 || format == 32) {
                            type.add(j ,TIME);
                        } else if (format == 14 || format == 31 || format == 57 || format == 58) {
                            type.add(j, DATE);
                        }else {
                            type.add(j, DATETIME);
                        }
                    }
                    /* 当是普通数字时 */
                    else {
                        BigDecimal bd = BigDecimal.valueOf(cell.getNumericCellValue());
                        if (MyUtil.subStrFirAppearLocate(bd.toPlainString(),".") == -1){
                            if(bd.toPlainString().length() > 20) {
                                type.add(j, VARCHAR);
                            }else if(bd.toPlainString().length() > 10) {
                                type.add(j, BIGINT);
                            } else {
                                type.add(j, INT);
                            }
                        } else {
                            type.add(j, DECIMAL);
                        }
                        // return bd.toPlainString();// 数值 这种用BigDecimal包装再获取plainString，可以防止获取到科学计数值
                    }
                    break;
                default:
                    throw new Exception("抱歉，暂时不支持Excel中存的部分格式");
            }
        }
        return type;
    }
}